const { Error } = require('./error');
const { Comment, Token, String, Parenthesis } = require('./token');

// pos is position in file
class ASTNode {
    constructor(children, pos) {
        this._children = children
        this.pos = pos
    }
    get(index) {
        return this.children()[index]
    }
    head() {
        return this.children()[0]
    }
    rest() {
        return this.children().slice(1)
    }
    children() {
        return this._children.slice(1, this._children.length - 1)
    }
    length() {
        return this.children().length
    }
}
class Define {
    constructor(pos, name, value) {
        this.pos = pos
        this.name = name
        this.value = value
    }
}

class SetValue {
    constructor(pos, name, value) {
        this.pos = pos
        this.name = name
        this.value = value
    }
}
class Cond {
    constructor(pos, condValues) {
        this.pos = pos
        this.condValues = condValues
    }
}
class Let {
    constructor(pos, vars, value) {
        this.pos = pos
        this.vars = vars
        this.value = value
    }
}
class Lambda {
    constructor(pos, args, exprs) {
        this.pos = pos
        this.args = args
        this.exprs = exprs
    }
}
class Operator {
    constructor(pos, operator, args) {
        this.pos = pos
        this.operator = operator
        this.args = args
    }
}
class Quote {
    constructor(pos, expr) {
        this.pos = pos
        this.expr = expr
    }
}

class Begin {
    constructor(pos, exprs) {
        this.pos = pos
        this.exprs = exprs
    }
}
class List {
    constructor(pos, value) {
        this.pos = pos
        this.value = value
    }
}
class Pair {
    constructor(pos, left, right) {
        this.pos = pos
        this.left = left
        this.right = right
    }
}
class Nil {
    constructor(pos) {
        this.pos = pos
    }
}
// (a b)
class Apply {
    constructor(pos, func, args) {
        this.pos = pos
        this.func = func
        this.args = args
    }
}

// return ast(begin)
function pass0(tklst) {
    let [err, ast, rest] = pass0_(
        [new Parenthesis("(", 0), new Token("begin", 0)].concat(tklst).concat(
            new Parenthesis(")", 0)))
    if (err !== null) {
        return err
    }
    if (rest.length > 0) {
        return new Error("括号外多余的东西", rest[0].pos)
    }
    return ast
}
// return err,ast,rest
function pass0_(tklst) {
    const first = tklst[0]
    expect(first, "(")
    let lst = [first]
    let rest = tklst.slice(1)
    while (rest.length > 0) {
        const tk = rest[0]
        if (tk.value == ")") {
            lst.push(tk)
            rest = rest.slice(1)
            return [null, new ASTNode(lst, first.pos), rest]
        } else if (tk.value == "(") {
            let err, node
            [err, node, rest] = pass0_(rest)
            if (err !== null) {
                return [err, null, []]
            }
            lst.push(node)
        } else if ((tk instanceof Comment)) {
            rest = rest.slice(1) // i++
        } else {
            lst.push(tk)
            rest = rest.slice(1)
        }
    }
    return [new Error("need )", first.pos), null, []]
}
function expect(tk, str) {
    if (tk.value !== str) {
        console.error("expect", tk.value, str)
    }
}
function pass01(ast) {
    return quoteListMerge(ast)
}
function pass1(ast) {
    // if (!(ast instanceof ASTNode)) {
    //     return pass1NodeQuote(ast)
    // }
    if (ast instanceof Error) {
        return ast
    }
    return pass1Node(ast)
}
function quote(ast) {
    if (ast instanceof ASTNode) {
        return ast.children()
    }
    return ast
}
function pass1NodeQuote(ast) {
    if (ast.value.length > 1 && ast.value[0] === "'") {
        return new Quote(ast, new Token(ast.value.slice(1), ast.pos))
    }
    return ast
}
function pass1Node(ast) {
    if (!isList(ast)) {
        if (ast instanceof Token) {
            return pass1NodeQuote(ast)
        }
        return ast
    }
    if (ast.length() === 0) {
        return new Nil(ast)
    }
    const lst0 = ast.children()
    let lst = quoteListMerge(lst0)
    const first = lst[0]
    const rest = lst.slice(1)
    if (first instanceof Token && first.value === 'quote') {
        if (rest.length !== 1) {
            return new Error("quote must length 1", ast.pos, ast)
        }
        return new Quote(ast.pos, quote(rest[0]))
    } else if (first instanceof Token && first.value === 'begin') {
        if (rest.length === 0) {
            return new Error("begin must have values", ast.pos, ast)
        }
        let es = pass1List(rest)
        return new Begin(ast.pos, es)
    } else if (first instanceof Token && first.value === 'cond') {
        if (rest.length === 0) {
            return new Error("cond must have condValues", ast.pos, ast)
        }
        let cvss = []
        for (let cv of rest) {
            if (!isList(cv)) {
                return new Error("cond must have cond-value list", ast.pos, ast)
            }
            // (c e1 e2 e3 ... )
            if (cv.length() < 2) {
                return new Error("cond-value at least have 2 values", ast.pos, ast)
            }
            const cvs = pass1List(cv.children())
            if (cvs instanceof Error) {
                return cvs
            }
            cvss.push(cvs)
        }
        return new Cond(ast.pos, cvss)
    } else if (first instanceof Token && first.value === 'if') {
        if (rest.length !== 3) {
            return new Error("if must have 3 parts", ast.pos, ast)
        }
        // c?a:b
        let cvs = []
        const c = pass1Node(rest[0])
        if (c instanceof Error) {
            return c
        }
        const a = pass1Node(rest[1])
        if (a instanceof Error) {
            return a
        }
        cvs.push([c, a])
        const b = pass1Node(rest[2])
        if (b instanceof Error) {
            return b
        }
        cvs.push([new Token("true", ast.pos), b])
        return new Cond(ast.pos, cvs)
    } else if (first instanceof Token && first.value === 'define') {
        if (rest.length <= 1) {
            return new Error("define must have a value", ast.pos, ast)
        }
        let name = rest[0]
        if (!(name instanceof Token)) {
            if (name.length() === 0) {
                return new Error("can't define empty list", ast.pos, ast)
            }
            const namesNode = name.rest()
            name = name.get(0)
            if (!(name instanceof Token)) {
                return new Error("define lambda's name must be a token", ast.pos, ast)
            }
            // (define (foo x) x)
            const lmd = lambda(ast, namesNode, rest.slice(1))
            if (lmd instanceof Error) {
                return lmd
            }
            return new Define(ast.pos, name.value, lmd)
        } else {
            const value = pass1Node(rest[1])
            if (value instanceof Error) {
                return value
            }
            return new Define(ast.pos, name.value, value)
        }
        return new Error("define only take two arguments", ast.pos, ast)
    } else if (first instanceof Token && first.value === 'set!') {
        if (rest.length <= 1) {
            return new Error("set! must have a value", ast.pos, ast)
        }
        let name = rest[0]
        if (!(name instanceof Token)) {
            return new Error("set! must be a name", ast.pos, ast)
        } else {
            const value = pass1Node(rest[1])
            if (value instanceof Error) {
                return value
            }
            return new SetValue(ast.pos, name.value, value)
        }
    } else if (first instanceof Token && first.value === 'lambda') {
        if (rest.length < 2) {
            return new Error("lambda at least two parts", ast.pos, ast)
        }
        // (lambda (x1 x2) e1 e2 e3...)
        const namesNode = rest[0]
        if (!(namesNode instanceof ASTNode)) {
            return new Error("lambda must have arguments list", ast.pos, ast)
        }
        return lambda(ast, namesNode.children(), rest.slice(1))
    } else if (first instanceof Token && first.value === 'let') {
        if (rest.length < 2) {
            return new Error("let at least have two parts", ast.pos, ast)
        }
        const vesNode = rest[0]
        if (!isList(vesNode)) {
            return new Error("let vars must be a list", ast.pos, ast)
        }
        if (vesNode.length() === 0) {
            return new Error("vars must have values", ast.pos, ast)
        }
        const vs = []
        const es = []
        for (let veNode of vesNode.children()) {
            if (!isList(veNode)) {
                return new Error("let var value must be a list", ast.pos, ast)
            }
            if (veNode.length() !== 2) {
                return new Error("let var value must be (var value)", ast.pos, ast)
            }
            const [vNode, eNode] = veNode.children()
            if (!(vNode instanceof Token)) {
                return new Error("let var must be a token", ast.pos, ast)
            }
            vs.push(vNode.value)
            const e = pass1Node(eNode)
            if (e instanceof Error) {
                return e
            }
            es.push(e)
        }
        const body = pass1List(rest.slice(1))
        if (body instanceof Error) {
            return body
        }
        const lmd = new Lambda(ast.pos, vs, body)
        return new Apply(ast.pos, lmd, es)
    } else {
        const values = []
        for (let v of rest) {
            const vv = pass1Node(v)
            if (vv instanceof Error) {
                return vv
            }
            values.push(vv)
        }
        const func = pass1Node(first)
        if (func instanceof Error) {
            return func
        }
        return new Apply(ast.pos, func, values)
    }
    // return new Error("unknown type", ast.pos, ast)
}
function isList(ast) {
    if (!(ast instanceof ASTNode)) {
        return false
    }
    return true
}
function pass1List(lst) {
    const r = []
    for (let e of lst) {
        const ee = pass1Node(e)
        if (ee instanceof Error) {
            return ee
        }
        r.push(ee)
    }
    return r
}

function lambda(ast, namesNode, exprsList) {
    const names = []
    for (let n of namesNode) {
        if (!(n instanceof Token)) {
            return new Error("lambda's arguments must be all names", ast.pos, ast)
        }
        names.push(n.value)
    }
    const exprs = []
    for (let e of exprsList) {
        const ee = pass1Node(e)
        if (ee instanceof Error) {
            return ee
        }
        exprs.push(ee)
    }
    return new Lambda(ast.pos, names, exprs)
}
function quoteListMerge(lst0) {
    // if (!(ast instanceof ASTNode)) {
    //     return ast
    // }
    // let lst = [ast._children[0]]
    // let lst0 = ast._children
    let lst = []
    if (lst0.length >= 2) {
        for (let i = 0; i < lst0.length; i++) {
            if (lst0[i].value === "'" && i + 1 < lst0.length) {
                lst.push(new ASTNode([
                    new Parenthesis("(", lst0[i].pos),
                    new Token("quote", lst0[i].pos),
                    lst0[i + 1],
                    new Parenthesis(")", lst0[i + 1].pos)
                ], lst0[i].pos))
                i++
            } else {
                lst.push((lst0[i]))
            }
        }
    } else {
        lst = lst0
    }
    // lst.push(lst0[lst0.length - 1])
    // ast._children = lst
    return lst
}
// return list of ast
function parse(tklst) {
    if (tklst instanceof Error) {
        return tklst
    }
    return pass1(pass0(tklst))
}
module.exports = {
    pass0, pass1, parse,
    ASTNode, Begin, Quote, Cond, Define, SetValue, Lambda, Apply
}